<style lang="scss" scoped></style>

<template>
<div>
  <input v-model="text" placeholder="请输入" @input="onDebounce" />
</div>
</template>

<script setup>
import { ref } from 'vue'

const text = ref('')
const counter = ref(0)
const onInput = function (event) {
  console.log(`发送第${counter.value++}次输入`, this, event)
  return 'baiqiang'
}

// 使用第三方库 underscore 实现防抖
// const onDebounce = _.debounce(onInput, 1000)

// 自己手写防抖
/**
 * 创建一个防抖函数，在事件停止触发后的延时时间内只执行一次
 * @param {function} fn 需要防抖的目标函数
 * @param {number} delay 防抖的延迟时间
 * @param {boolean} immediate 防抖模式，是否立即执行
 * @param {function} resultCallback 目标对象返回值的回调函数
 * @returns {function} 防抖函数
 * @author baiqiang
*/
// function debounce(fn, delay, immediate = false, resultCallback) {
  // 保存每次定时器操作，在下一次规定时间内，重复调用提供取消信息
  // let timer = null
  // 作为判断当前为立即执行 or 防抖模式
  // let isInvoke = false
  // 防抖的基础实现
  // const _debounce = () => {
  //   if (timer) clearTimeout(timer)
  //   timer = setTimeout(() => {
  //     fn()
  //   }, delay)
  // }

  /**
   * 优化一：使用防抖函数时，传入函数内可接受 this 和 event 事件参数，该 this 参数直接指向于绑定的元素本身
   * 1、在我们尚未设置时，this取决于函数调用位置，且我们内部采用箭头函数，不受this影响，因此直接指向于Window对象
   * 2、正常情况this会绑定于显式调用，这也是我们所需求的，元素本身调用的是_debounce函数，因此该函数不能够使用箭头函数，其次将传入的fn函数参数绑定上_debounce的this
   * 3、而event事件一样会通过调用方传递到_debounce，因此可以对传入的fn函数使用apply绑定对应this和传入对应剩余参数即可
  */
  // const _debounce = function (...args) {
  //   if (timer) clearTimeout(timer)
  //   timer = setTimeout(() => {
  //     fn.apply(this, args) // 绑定 this 为元素本身以及 event 事件
  //   }, delay)
  // }

  /**
   * 优化二：立即执行功能
   * 1、立即执行主要作用为在用户第一次输入内容时，先立刻请求一次，这种立即见效的效果会给用户带来先入为主的良好感观，是利用人的心理想法的一种做法
   * 2、这种都是需要根据具体场景所决定，因此需要给工具使用者选择模式的机会，这时候需要在第三个参数中加入选项来进行调控，该参数名命名为immediate(立即)
   * 3、后续防抖效果不变
  */
  // const _debounce = function (...args) {
  //   if (timer) clearTimeout(timer)
  //   // 判断初次是否立即执行
  //   if (immediate && !isInvoke) {
  //     fn.apply(this, args)
  //     isInvoke = true
  //   } else {
  //     timer = setTimeout(() => {
  //       fn.apply(this, args)
  //       // 防抖模式结束,下次输入内容立即执行
  //       isInvoke = false
  //     }, delay)
  //   }
  // }

  /**
   * 优化四：返回值
   * 如果在使用防抖函数时，传入的内容是有可能有对应的返回值的，我们要怎么拿到这个返回值呢？
   * 我们主要有两种思路：
   * 1、常见的回调参数，对debounce防抖函数添加一个回调函数参数，在debounce内部将参数一inputChange的返回值接收后，调用该回调函数参数时，将该返回值传递进去
   * 2、
  */
  // const _debounce = function (...args) {
  //   if (timer) clearTimeout(timer)
  //   // 判断初次是否立即执行
  //   if (immediate && !isInvoke) {
  //     const result = fn.apply(this, args)
  //      // 将目标对象返回值返回后,能够进行回调执行处理
  //     if (resultCallback) resultCallback(result)
  //     isInvoke = true
  //   } else {
  //     timer = setTimeout(() => {
  //       const result = fn.apply(this, args)
  //        // 将目标对象返回值返回后,能够进行回调执行处理
  //      if (resultCallback) resultCallback(result)
  //       // 防抖模式结束,下次输入内容立即执行
  //       isInvoke = false
  //     }, delay)
  //   }
  // }

  /**
   * 优化三：取消执行功能
   * 1、我们前面了解防抖应用场景时，知道防抖函数不止可以运用于搜索引擎中，还可以用在各类表单上
   * 2、假如用户输入内容进行请求中，觉得请求时间太长或者干脆不是想要的，就点击了对应的取消请求按钮或者直接干脆退出界面了，那我们该次请求是有必要进行取消的，因为该次请求没有实际意义那么在面对该情况时，我们应该怎么做？
   * 3、首先函数本身也是一个对象，因此可以往函数身上添加属性方法
   * 4、我们可以在_debounce的身上封装取消功能，因为_debounce作用到元素本身
   * 5、封装函数方法名为cancel(取消)，当调用该方法时，判断timer是否有值，从而推断是否有定时器正在运行准备发起网络请求，若有值则消除网络请求，若没有则无需变化。同时作为取消方法，代表着"初始化"的含义，最好将timer和isInvoke重置一下。当该工具类迭代次数起来，封装更为完善，功能更多后，则可以专门创建一个初始化函数对这些内容进行封装
  */
  //   _debounce.cancel = function () {
  //     if (timer) clearTimeout(timer)
  //     timer = null
  //     isInvoke = false
  //   }

  //   return _debounce
// }

// 完整代码
function debounce(fn, wait, immediate = false, resultCallback) {
  // 保存每次定时器操作，在下一次规定时间内，重复调用提供取消信息
  let timer = null
  // 作为判断当前立即执行 or 防抖模式
  let isInvoke = false
  const _debounce = function (...args) {
    // 排除第一次调用无 timer, 其余规定时刻清除上一次定时器
    if (timer) clearTimeout(timer)
    // 判断初次是否立即执行
    if (immediate && !isInvoke) {
      const result = fn.apply(this, args)
      // 将目标对象返回值返回后，能够进行回掉执行处理
      if (resultCallback) resultCallback(result)
      // 初次立即执行，后续正常防抖模式
      isInvoke = true
    } else {
      // 防抖模式
      timer = setTimeout(() => {
        const result = fn.apply(this, args)
        // 将目标对象返回值返回后，能够进行回掉执行处理
        if (resultCallback) resultCallback(result)
        // 防抖模式结束，下次输入内容立即执行
        isInvoke = false
      }, wait)
    }
  }

  // 封装取消防抖功能
  _debounce.cancel = function () {
    if (timer) clearTimeout(timer)
    timer = null
    isInvoke = false
  }

  return _debounce
}

const onDebounce = debounce(onInput, 1000)
</script>